﻿using System;
using System.Collections.Generic;
using System.Text;

class ExpressionCalculator
{
    static void Main()
    {
        Console.WriteLine("You can use the following mathematic functions: ");
        Console.WriteLine("sqrt, log, ln, pow, sin, cos, tg(tan), cotg(cotan), \n max, min(max and min can be used only with 2 arguments)");
        Console.WriteLine("You can use the following operators: +, -, *, /, ^, (, ) \n");
        while (true)
        {
            try
            {
                Console.WriteLine("Please, enter an expression: ");
                string expression = Console.ReadLine();
                List<Token> RPN = FromInfixToRPN(expression);
                double answer = ExecuteRVN(RPN);
                Console.WriteLine("Answer: " + answer + "\n");
            }
            catch (Exception e)
            {

                Console.WriteLine("Error: " + e.Message);
                Console.WriteLine();
            }
        }
    }

    static public double ExecuteRVN(List<Token> expression)
    {
        // The stack of operators which is used to store the operands
        Stack<double> operands = new Stack<double>();

        // Passing through all the tokens of the expression
        for (int i = 0; i < expression.Count; i++)
        {
            // If the token is a number - push it onto the stack.
            if (expression[i].Type == TokenType.Number)
            {
                operands.Push(double.Parse(expression[i].Value));
                continue;
            }

            // If the token is a constant - push it onto the stack.
            if (expression[i].Type == TokenType.Constant)
            {
                switch (expression[i].Value)
                {
                    case "pi":
                        operands.Push(Math.PI);
                        break;
                    case "e":
                        operands.Push(Math.E);
                        break;
                    default:
                        throw new Exception("Unknown constant " + expression[i].Value);
                }
                continue;
            }

            // If the token is an operator - evaluate the operator, with the poped values as operands.
            if (expression[i].Type == TokenType.Operator)
            {
                switch (expression[i].Value)
                {
                    case "+":
                        operands.Push(operands.Pop() + operands.Pop());
                        break;
                    case "-":
                        operands.Push(-operands.Pop() + operands.Pop());
                        break;
                    case "*":
                        operands.Push(operands.Pop() * operands.Pop());
                        break;
                    case "/":
                        operands.Push(1 / operands.Pop() * operands.Pop());
                        break;
                    case "^":
                        double y = operands.Pop();
                        double x = operands.Pop();
                        operands.Push(Math.Pow(x, y));
                        break;
                    case "~":
                        operands.Push(-operands.Pop());
                        break;
                    default:
                        throw new Exception("Unknown operator " + expression[i].Value);
                }
            }

            // If the token is an function - evaluate the operator, with the poped values as arguments.
            if (expression[i].Type == TokenType.Function)
            {
                double x;
                double y;
                switch (expression[i].Value)
                {
                    case "sqrt":
                        operands.Push(Math.Sqrt(operands.Pop()));
                        break;
                    case "ln":
                        operands.Push(Math.Log(operands.Pop()));
                        break;
                    case "log":
                        y = operands.Pop();
                        x = operands.Pop();
                        operands.Push(Math.Log(x, y));
                        break;
                    case "pow":
                        y = operands.Pop();
                        x = operands.Pop();
                        operands.Push(Math.Pow(x, y));
                        break;
                    case "sin":
                        operands.Push(Math.Sin(operands.Pop()));
                        break;
                    case "cos":
                        operands.Push(Math.Cos(operands.Pop()));
                        break;
                    case "tg":
                    case "tan":
                        operands.Push(Math.Tan(operands.Pop()));
                        break;
                    case "cotg":
                    case "cotan":
                        operands.Push(1.0 / Math.Tan(operands.Pop()));
                        break;
                    case "max":
                        operands.Push(Math.Max(operands.Pop(), operands.Pop())); // Note: Takes only two arguments
                        break;
                    case "min":
                        operands.Push(Math.Min(operands.Pop(), operands.Pop()));// Note: Takes only two arguments
                        break;
                    default:
                        throw new Exception("Invalid operator or function " + expression[i]);
                }
            }
        }

        // If there are more than one value in the stack -  the expression is invalid.
        if (operands.Count != 1)
        {
            throw new Exception("Invalid expression!");
        }
        else // Otherwise - that value is the result of the calculation.
        {
            return operands.Pop();
        }
    }

    static List<Token> FromInfixToRPN(string expression)
    {
        // The returned list of tokens.
        List<Token> output = new List<Token>();
        // The stack of operators which is used to temporarily store the operators
        Stack<Token> operators = new Stack<Token>();
        // Flag which indicate whether the previous token is number or constant. This is used when a 
        // minus sign is met to determine if the it is unary minus or ordinary minus.
        bool previousIsNumeric = false;

        // Passing through all the chars of the expression
        for (int index = 0; index < expression.Length; index++)
        {
            // If the token is white space
            if (expression[index] == ' ')
            {
                continue;
            }

            // If the token is number
            if (expression[index] >= '0' && expression[index] <= '9')
            {
                StringBuilder number = new StringBuilder();
                number.Append(expression[index]);
                index++;
                // Looping through all the chars of the number and store them in a StringBuilder
                while ((index < expression.Length) && ((expression[index] >= '0' && expression[index] <= '9') || expression[index] == '.'))
                {
                    number.Append(expression[index]);
                    index++;
                }

                output.Add(new Token(number.ToString(), TokenType.Number));
                previousIsNumeric = true;
                // If we are at the end of the expression string
                if (index >= expression.Length)
                {
                    break;
                }
            }

            // If the token is a function or constant
            if (expression[index] >= 'a' && expression[index] <= 'z')
            {
                StringBuilder function = new StringBuilder(expression[index].ToString());
                index++;
                // Looping through all the chars of the number and store them in a StringBuilder
                while ((index < expression.Length) && (expression[index] >= 'a' && expression[index] <= 'z'))
                {
                    function.Append(expression[index]);
                    index++;
                }

                string value = function.ToString().ToLower();
                // If the token is a constant ... else it is a function
                if (value == "pi" || value == "e")
                {
                    output.Add(new Token(value.ToLower(), TokenType.Constant));
                    previousIsNumeric = true;
                }
                else
                {
                    operators.Push(new Token(value.ToLower(), TokenType.Function));
                    previousIsNumeric = false;
                }

                if (index >= expression.Length)
                {
                    break;
                }
            }

            // If the token is a function argument separator (a comma):
            if (expression[index] == ',')
            {
                // Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. 
                while (operators.Count > 0 && operators.Peek().Type != TokenType.LeftParenthesis)
                {
                    output.Add(operators.Pop());
                }

                // If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched.
                previousIsNumeric = false;

                if (operators.Count == 0)
                {
                    throw new Exception("Parentheses mismatch or function argument separator was misplaced.");
                }
                continue;
            }

            // If the token is operator
            Token currentOperator = new Token("", TokenType.Unknown);
            if (expression[index] == '+' || (expression[index] == '-' && previousIsNumeric)) // If the operator is plus or regular minus
            {
                currentOperator.Value = expression[index].ToString();
                currentOperator.Type = TokenType.Operator;
                currentOperator.Precedence = 2;
            }
            else if (expression[index] == '-' && !previousIsNumeric) // If the operator is unary minus
            {
                currentOperator.Value = "~";
                currentOperator.Type = TokenType.Operator;
                currentOperator.Precedence = 5;
            }
            else if (expression[index] == '*' || expression[index] == '/') // If the operator is multiplication or division
            {
                currentOperator.Value = expression[index].ToString();
                currentOperator.Type = TokenType.Operator;
                currentOperator.Precedence = 3;
            }
            else if (expression[index] == '^') // If the operator is power sign
            {
                currentOperator.Value = expression[index].ToString();
                currentOperator.Type = TokenType.Operator;
                currentOperator.Precedence = 4;
                currentOperator.IsLeftToRight = false;
            }

            if (currentOperator.Type == TokenType.Operator)
            {
                /* *    
                * while there is an operator token, o2, at the top of the stack, and
                *         either o1 is left-associative and its precedence is less than or equal to that of o2,
                *         or o1 has precedence less than that of o2,
                *     pop o2 off the stack, onto the output queue;
                * */
                while (operators.Count > 0 && operators.Peek().Type == TokenType.Operator)
                {
                    Token previous = operators.Peek();
                    if ((currentOperator.IsLeftToRight && currentOperator.Precedence <= previous.Precedence) || currentOperator.Precedence < previous.Precedence)
                    {
                        output.Add(operators.Pop());
                    }
                    else
                    {
                        break;
                    }
                }
                operators.Push(currentOperator);
                previousIsNumeric = false;
                continue;
            }

            //If the token is a left parenthesis
            if (expression[index] == '(')
            {
                operators.Push(new Token("(", TokenType.LeftParenthesis));
                previousIsNumeric = false;
                continue;
            }

            //If the token is a right parenthesis
            if (expression[index] == ')')
            {
                // Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
                while (operators.Count > 0 && operators.Peek().Type != TokenType.LeftParenthesis)
                {
                    output.Add(operators.Pop());
                }

                // If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
                if (operators.Count == 0)
                {
                    throw new Exception("Parentheses mismatch.");
                }
                // Pop the left parenthesis from the stack, but not onto the output queue.
                operators.Pop();
                // If the token at the top of the stack is a function token, pop it onto the output queue.
                if (operators.Count > 0 && operators.Peek().Type == TokenType.Function)
                {
                    output.Add(operators.Pop());
                }

                previousIsNumeric = false;
                continue;
            }
        }

        while (operators.Count > 0)
        {
            if (operators.Peek().Type == TokenType.LeftParenthesis || operators.Peek().Type == TokenType.RightParenthesis)
            {
                throw new Exception("Parentheses mismatch.");
            }
            output.Add(operators.Pop());
        }

        return output;
    }
}

